package com.moons.uvcdemo;

import android.Manifest;
import android.annotation.SuppressLint;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.PorterDuff;
import android.graphics.Rect;
import android.graphics.RectF;
import android.os.Bundle;
import android.os.Environment;
import android.util.Log;
import android.util.Size;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;

import com.afollestad.materialdialogs.folderselector.FileChooserDialog;
import com.moons.uvcdemo.util.ARUtil;
import com.moons.uvcdemo.util.FileUtil;
import com.tbruyelle.rxpermissions2.RxPermissions;
import com.xqd.sdk.libaicamera.AICamera;
import com.xqd.sdk.libaicamera.AICameraManager;
import com.xqd.sdk.libaicamera.FeatureComparator;
import com.xqd.sdk.libaicamera.face.FaceObject;
import com.xqd.sdk.libaicamera.face.FaceObjectParser;
import com.xqd.sdk.libaicamera.utils.Utils;

import java.io.File;
import java.lang.ref.WeakReference;
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.TimeUnit;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import io.reactivex.functions.Consumer;


/**
 * 此Demo主要演示1:1情况下的人脸识别<p>
 * 第一个"1"为  <a href="https://gitee.com/my10moons/uvc_face_registration_demo">从人脸图片获取的特征值</a> <br>
 * 第二个"1"为当前摄像头前的人脸特征值，有这两个就可以对比相似度，一般大于0.4即可认为是同一个人。<br>
 *
 * @author 10moons
 */
public class ShotRegisterActivity extends AppCompatActivity implements FileChooserDialog.FileCallback {
    private static final String TAG = "MainActivity";

    private SurfaceView mSurfaceView;
    private SurfaceView mFaceSurfaceView;
    private TextView mVersionTextView;
    /**
     * 选择特征值文件按钮，如demo4顺利完成，默认文件保存在/sdcard/ar_face_data/ar_face_feature/路径下<br/>
     * 以.txt结尾文件
     */
    private Button bt_chose_feature_file;
    private TextView tv_similarity;

    private ArrayBlockingQueue<byte[]> mFrameDataQueue;
    private ArrayBlockingQueue<byte[]> mFaceDataQueue;
    private ArrayBlockingQueue<Bitmap> mBitmapFrameQueue;

    private DecodeThread mDecodeThread;
    private DrawFaceThread mDrawFaceThread;
    private DrawFrameThread mDrawFrameThread;

    private AICamera mAICamera;

    private long mLastFrameTime;
    private long mLastCompareTime;

    private final Object mFrameCountLock = new Object();

    private boolean isPreviewAvailable;
    private boolean isOverlayAvailable;

    private int mNoFaceFrameCount = 0;
    AICameraManager mAICameraManager;
    private WeakReference<ShotRegisterActivity> mMainActivityWeakReference;

    /**
     * 需要对比特征值的目标文件，一般在/sdcard/ar_face_data/ar_face_feature/路径下
     */
    private File targetFeatureFile;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_recognize);
        mMainActivityWeakReference = new WeakReference<>(this);

        initPermission();
        initViews();
        initDeviceListener();
        initQueues();
        initThread();
        mLastFrameTime = System.currentTimeMillis();
    }

    /**
     * 初始化设备监听
     */
    private void initDeviceListener() {

        mAICameraManager = AICameraManager.instance();

        mAICameraManager.setDeviceEventListener(new AICameraManager.DeviceEventListener() {
            @Override
            public void onDeviceAdded(AICamera aiCamera) {
                Log.d(TAG, "onDeviceAdded: " + aiCamera.getPath());
                if (aiCamera.open()) {
                    List<Size> previewSizes = aiCamera.getSupportPreviewSizes();
                    if (previewSizes.size() > 0) {
                        for (Size sz : previewSizes) {
                            Log.d(TAG, String.format("%d*%d", sz.getWidth(), sz.getHeight()));
                            if (is720P(sz)) {
                                Log.d(TAG, "onDeviceAdded: set 720p");
                                aiCamera.setPreviewSize(sz);
                            }
                        }
                    }

                    //设置回调
                    aiCamera.setCaptureCallback(mCaptureCallback);

                    aiCamera.startCapture();

                    mAICamera = aiCamera;
                }


            }

            @Override
            public void onDeviceRemoved(AICamera aiCamera) {
                aiCamera.setCaptureCallback(null);
                aiCamera.stopCapture();
                aiCamera.close();
                mAICamera = null;
            }
        });
        mAICameraManager.start();

    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (mAICamera != null) {

            mAICamera.stopCapture();

            mAICamera.release();
        }
//        if (mAICameraManager != null) {
//            mAICameraManager.close();
//        }

    }

    private boolean is720P(Size size) {
        return ((size.getWidth() == 1280) && (size.getHeight() == 720) || ((size.getWidth() == 720) && (size.getHeight() == 1280)));
    }


    /**
     * 为了读取demo4生成的特征值，默认需要sdcard权限
     * <br/>
     *
     * @see <a href="https://gitee.com/my10moons/uvc_face_registration_demo">从人脸图片获取特征值</a>
     */
    @SuppressLint("CheckResult")
    private void initPermission() {
        final RxPermissions rxPermissions = new RxPermissions(this);
        rxPermissions.request(Manifest.permission.READ_EXTERNAL_STORAGE)
                .subscribe(new Consumer<Boolean>() {
                    @Override
                    public void accept(Boolean aBoolean) throws Exception {
                        if (!aBoolean) {
                            Toast.makeText(ShotRegisterActivity.this, "请授予读取权限，否则本demo不能正常演示。", Toast.LENGTH_SHORT).show();
                        }

                    }
                }, new Consumer<Throwable>() {
                    @Override
                    public void accept(Throwable throwable) throws Exception {
                        throwable.printStackTrace();

                    }
                });

    }

    private void initViews() {
        tv_similarity = findViewById(R.id.tv_similarity);
        mVersionTextView = findViewById(R.id.version);
        mVersionTextView.setText(AICamera.getSDKVersion());
        mSurfaceView = findViewById(R.id.surfaceView_camera_preview);
        mSurfaceView.setZOrderOnTop(false);
        mFaceSurfaceView = findViewById(R.id.surfaceView_face_view);
        mSurfaceView.getHolder().addCallback(new SurfaceHolder.Callback() {
            @Override
            public void surfaceCreated(SurfaceHolder holder) {
                holder.setFormat(PixelFormat.RGBA_8888);
                isPreviewAvailable = true;
            }

            @Override
            public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {

            }

            @Override
            public void surfaceDestroyed(SurfaceHolder holder) {
                isPreviewAvailable = false;
                Log.d(TAG, "mSurfaceView    surfaceDestroyed: ");
            }
        });
        mFaceSurfaceView.setZOrderOnTop(true);
        mFaceSurfaceView.setZOrderMediaOverlay(true);
        mFaceSurfaceView.getHolder().addCallback(new SurfaceHolder.Callback() {
            @Override
            public void surfaceCreated(SurfaceHolder holder) {
                holder.setFormat(PixelFormat.TRANSLUCENT);
                isOverlayAvailable = true;
            }

            @Override
            public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {

            }

            @Override
            public void surfaceDestroyed(SurfaceHolder holder) {
                isOverlayAvailable = false;
                Log.d(TAG, "mFaceSurfaceView    surfaceDestroyed: ");
            }
        });

        bt_chose_feature_file = findViewById(R.id.bt_chose_feature_file);
        bt_chose_feature_file.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (mMainActivityWeakReference.get() != null) {

                    FileChooserDialog mFileChooserDialog = new FileChooserDialog.Builder(getApplicationContext())
                            .initialPath(Environment.getExternalStorageDirectory().getPath())  // changes initial path, defaults to external storage directory
                            .mimeType("txt/plain") // Optional MIME type filter
                            .extensionsFilter(".txt") // Optional extension filter, will override mimeType()
                            .tag("optional-identifier")
//                            .goUpLabel("Up") // custom go up label, default label is "..."
                            .build();
                    bt_chose_feature_file.setClickable(false);
                    mFileChooserDialog.show(mMainActivityWeakReference.get()); // an AppCompatActivity which implements FileCallback
                }
            }
        });

    }

    private void initQueues() {
        mFrameDataQueue = new ArrayBlockingQueue<>(2, true);
        mFaceDataQueue = new ArrayBlockingQueue<>(2);
        mBitmapFrameQueue = new ArrayBlockingQueue<>(2);
        mFrameDataQueue.clear();
        mFaceDataQueue.clear();
        mBitmapFrameQueue.clear();
    }

    private void initThread() {
        mDecodeThread = new DecodeThread();
        mDrawFaceThread = new DrawFaceThread();
        mDrawFrameThread = new DrawFrameThread();
        mDecodeThread.start();
        mDrawFaceThread.start();
        mDrawFrameThread.start();
    }

//    private void initCamera() {
//        try {
//            mAICamera = AICamera.open("/dev/video0");
//            mAICamera.setCaptureCallback(mCaptureCallback);
//            mAICamera.startCapture();
//        } catch (IOException e) {
//            e.printStackTrace();
//        }
//    }

    AICamera.CaptureCallback mCaptureCallback = new AICamera.CaptureCallback() {


        @Override
        public void onFrameData(byte[] data, AICamera camera) {

            if (!isPreviewAvailable) {
                return;
            }
//            Log.d(TAG, "onJpegFrame");
            long currentTime = System.currentTimeMillis();
            if ((currentTime - mLastFrameTime) < 20) {
//                Log.d(TAG, "ignore one frame");
            }
            synchronized (mFrameCountLock) {
                mNoFaceFrameCount++;
                if (mNoFaceFrameCount >= 10) {
                    clearOverlay(mFaceSurfaceView);
                    mNoFaceFrameCount = 0;
                }
            }
            if (!mFrameDataQueue.offer(data)) {
//                Log.d(TAG, "mFrameQueue failed: size ==> " + mFrameDataQueue.size() + " ,capacity ==> " + mFrameDataQueue.remainingCapacity() + "");
                mFrameDataQueue.clear();
                mFrameDataQueue.offer(data);
            }
        }

        @Override
        public void onFaceData(byte[] data, AICamera camera) {
            if (!isPreviewAvailable) {
                return;
            }
//            Log.d(TAG, "onDataFrame");


            if (!mFaceDataQueue.offer(data)) {
//                Log.d(TAG, "failed to offer a data queue, clear");
                mFaceDataQueue.clear();
                mFaceDataQueue.offer(data);
            }
        }
    };

    private void DrawFrame(final Bitmap bitmap) {
        //        Log.d(TAG, "DrawFrame start");
        Rect dest = new Rect(0, 0, mSurfaceView.getWidth(), mSurfaceView.getHeight());
        Paint paint = new Paint();
        //paint.setAntiAlias(true);
        paint.setDither(true);
        paint.setFilterBitmap(true);
        Rect src = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight());
        Canvas canvas = mSurfaceView.getHolder().lockCanvas();
        if (canvas == null) {
            return;
        }
        canvas.drawBitmap(bitmap, src, dest, paint);
        mSurfaceView.getHolder().unlockCanvasAndPost(canvas);
        if (!bitmap.isRecycled()) {
            bitmap.recycle();
        }
//        Log.d(TAG, "DrawFrame end");
    }

    /**
     * 画人脸框
     *
     * @param faceObjectList 人脸数据信息，可能有多个
     */
    private void DrawFaces(final List<FaceObject> faceObjectList, Size size) {
//        Log.d(TAG, "DrawFaces");
        float widthScale = (float) mFaceSurfaceView.getWidth() / size.getWidth();
        float heightScale = (float) mFaceSurfaceView.getHeight() / size.getHeight();
        Canvas canvas = mFaceSurfaceView.getHolder().lockCanvas();
        if (canvas == null) {
            return;
        }
        canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);

        for (FaceObject faceObject : faceObjectList) {
            Paint paint = new Paint();
            paint.setStrokeWidth(4);
            paint.setStyle(Paint.Style.STROKE);
            paint.setColor(Color.WHITE);
            paint.setAntiAlias(true);


            RectF rect = new RectF();


            rect.left = faceObject.rect().left;
            rect.right = faceObject.rect().right;
            rect.top = faceObject.rect().top;
            rect.bottom = faceObject.rect().bottom;

            float originFaceWidth = rect.width();
            float originFaceHeight = rect.height();


            float finalWidth = originFaceWidth * widthScale;
            float finalHeight = originFaceHeight * heightScale;
            float orginX = rect.left + originFaceWidth / 2;
            float orginY = rect.top + originFaceHeight / 2;
            float x = orginX * widthScale;
            float y = orginY * heightScale;

            rect.left = x - finalWidth / 2;
            rect.right = x + finalWidth / 2;
            rect.top = y - finalHeight / 2;
            rect.bottom = y + finalHeight / 2;

            canvas.drawRect(rect, paint);
        }
        mFaceSurfaceView.getHolder().unlockCanvasAndPost(canvas);
    }

    byte[] faceFeatures;

    @Override
    public void onFileSelection(@NonNull FileChooserDialog dialog, @NonNull File file) {
        targetFeatureFile = file;

        faceFeatures = FileUtil.getInstance().readUserFaceFromFile(targetFeatureFile);


        Toast.makeText(this, "已选择" + file.getName(), Toast.LENGTH_SHORT).show();
    }

    @Override
    public void onFileChooserDismissed(@NonNull FileChooserDialog dialog) {
        bt_chose_feature_file.setClickable(true);
    }


    private class DrawFrameThread extends Thread {

        private boolean mRunning;

        public DrawFrameThread() {
            mRunning = false;
        }

        @Override
        public synchronized void start() {
            mRunning = true;
            super.start();
        }

        @Override
        public void run() {
            while (mRunning) {
                try {
                    Bitmap bitmap = mBitmapFrameQueue.poll(2, TimeUnit.MILLISECONDS);
                    if (!isPreviewAvailable) {
                        if (bitmap != null) {
                            bitmap.recycle();
                            bitmap = null;
                            continue;
                        }
                    }
                    if (bitmap != null) {
                        DrawFrame(bitmap);
                    } else {
                        //Log.d(TAG, "poll data timeout");
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }

        @Override
        public void interrupt() {
            mRunning = false;
            super.interrupt();
        }
    }

    private class DrawFaceThread extends Thread {

        private boolean mRunning;

        public DrawFaceThread() {
            mRunning = false;
        }

        @Override
        public synchronized void start() {
            mRunning = true;
            super.start();
        }

        @Override
        public void run() {
            while (mRunning) {
                try {
                    byte[] data = mFaceDataQueue.poll(1, TimeUnit.MILLISECONDS);
                    if (data != null && mAICamera != null) {

                        Size size = Utils.getSizeFromFaceData(data);

                        FaceObjectParser faceObjectParser = mAICamera.getFaceObjectParser();
                        if (faceObjectParser != null) {


                            List<FaceObject> faceObjectList = faceObjectParser.getFaceObjectList(data, 0);

                            if (faceObjectList != null && !faceObjectList.isEmpty()) {

                                synchronized (mFrameCountLock) {
                                    mNoFaceFrameCount = 0;
                                    DrawFaces(faceObjectList, size);

                                    compareWithLocalFeatureFile(faceObjectList);
                                }
                            }
                        }

                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }

        @Override
        public void interrupt() {
            mRunning = false;
            super.interrupt();
        }
    }

    private void compareWithLocalFeatureFile(List<FaceObject> faceObjects) {
//        if (System.currentTimeMillis() - mLastCompareTime < 1000) {
//            //限制log输出，正式环境下请去除
//            return;
//        }
//        Log.d(TAG, "compareWithLocalFeatureFile: ");


        if (mAICamera == null || targetFeatureFile == null || !targetFeatureFile.exists()) {
            Log.i(TAG, "需要对比的目标文件不存在，请点击右下角按钮选择由 Getdatademo 生成的txt文件");

            return;
        }
        FeatureComparator featureComparator = mAICamera.getFeatureComparator();
        if (featureComparator == null) {
            return;
        }

        if (faceFeatures == null) {
            return;
        }
        //从 Getdatademo中，我们最后保存的是人脸特征值，所以这里读取的是人脸特征值数组


        for (FaceObject faceObject : faceObjects) {


//            Log.d(TAG, "compareWithLocalFeatureFile: " + faceObject.id());

            if (faceObject.hasFeatures()) {

                final float similarity = featureComparator.getSimilarity(faceObject.features(), ARUtil.byteArr2floatArr(faceFeatures));
//                Log.d(TAG, "compareWithLocalFeatureFile: similarity=" + similarity);
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        tv_similarity.setText(String.valueOf(similarity));
                    }
                });
            }

        }


    }


    private class DecodeThread extends Thread {

        private boolean mRunning;

        public DecodeThread() {
            mRunning = false;
        }

        @Override
        public synchronized void start() {
            mRunning = true;
            super.start();
        }

        @Override
        public void run() {
            while (mRunning) {
                try {
                    byte[] frame = mFrameDataQueue.poll(2, TimeUnit.MILLISECONDS);
                    if (frame != null) {
//                        Log.d(TAG, "decode start");
                        Bitmap bitmap = BitmapFactory.decodeByteArray(frame, 0, frame.length);
                        if (bitmap != null) {
//                            Log.d(TAG, "decode end");
                            if (!mBitmapFrameQueue.offer(bitmap)) {
//                                Log.d(TAG, "could not offer to mBitmapFrameQueue");
                            }
                        }
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }

        @Override
        public void interrupt() {
            mRunning = false;
            super.interrupt();
        }
    }

    public static void clearOverlay(SurfaceView outputView) {
        Canvas canvas = null;
        try {
            canvas = outputView.getHolder().lockCanvas();
            if (canvas == null) {
                return;
            }
            canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                outputView.getHolder().unlockCanvasAndPost(canvas);
            } catch (Exception e) {
                Log.e("SurfaceDraw", "can not unlock canvas");
            }
        }
    }
}
